package lumis.portal.presentation.perspective.user;

import java.util.EmptyStackException;
import java.util.Stack;

import lumis.portal.presentation.core.LayoutFileTag;
import lumis.portal.presentation.tag.page.PageMarkupFilter;

import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.parser.XmlTag;

/**
 * Markup filter that processes layout file tags, inserting dynamic data 
 * according the <em>auto</em> generate attribute. 
 *
 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
 * @since 6.0.0
 */
public class UserPerspectiveMarkupFilter extends PageMarkupFilter
{
	public UserPerspectiveMarkupFilter(boolean stripLayoutFileTagsAndAttributes)
	{
		super(stripLayoutFileTagsAndAttributes);
	}

	/**
	 * Class used as entry of {@link PageAutoGenerateMarkupFilter#autoGenerateStack}.
	 * <p>
	 * Contains the tag that contains the auto attribute and its value.
	 * 
	 * 
	 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
	 * @since 6.0.0
	 */
	private static class AutoGenerateStackEntry
	{
		/**
		 * @since 6.0.0
		 */
		final private ComponentTag tag;
		/**
		 * @since 6.0.0
		 */
		final private boolean autoGenerate;
		
		/**
		 * Basic constructor for {@link AutoGenerateStackEntry}.
		 * <p>
		 * Uses a tag name and the auto generate value. 
		 * 
		 * @param tagName the tag.
		 * @param autoGenerateValue the auto generate value.
		 * @since 6.0.0
		 */
		public AutoGenerateStackEntry(ComponentTag tag, boolean autoGenerateValue)
		{
			if(tag == null)
				throw new IllegalArgumentException("tag cannot be null");
			
			this.tag = tag;
			this.autoGenerate = autoGenerateValue;
		}

		/**
		 * Returns the tag.
		 * @return the tag.
		 * @since 6.0.0
		 */
		public ComponentTag getTag()
		{
			return tag;
		}

		/**
		 * Returns the auto generate value.
		 * @return the auto generate value.
		 * @since 6.0.0
		 */
		public boolean isAutoGenerate()
		{
			return autoGenerate;
		}
	}
	
	/**
	 * A stack used to manage the auto generate state.
	 * @since 6.0.0
	 */
	private Stack<AutoGenerateStackEntry> autoGenerateStack = new Stack<AutoGenerateStackEntry>();
	
	/**
	 * Indicates if the <code>bodyHeader</code> component has been included.
	 * @since 6.0.0
	 */
	private ComponentTag bodyHeaderComponentTag = null;
	
	/**
	 * The <code>bodyFooter</code> component included automatically.
	 * @since 6.0.0
	 */
	private boolean bodyFooterIncluded = false;

	/**
	 * The <code>headScripts</code> component included automatically.
	 * @since 6.0.0
	 */
	private ComponentTag headScriptsComponentTag = null;
	
	/**
	 * Indicates if the <code>metas</code> component has been included.
	 * @since 6.0.0
	 */
	private boolean headMetasIncluded = false;
	
	/**
	 * Indicates if the <code>links</code> component has been included.
	 * @since 6.0.0
	 */
	private boolean headLinksIncluded = false;
	
	/**
	 * Indicates if the <code>title</code> component has been included.
	 * @since 6.0.0
	 */
	private boolean titleIncluded = false;
	
	/**
	 * Indicates if the HTML has a <code>head</code> element included.
	 * @since 6.0.0
	 */
	private boolean existsHead = false;
	
	@Override
	protected ComponentTag beforeProcessTag(ComponentTag componentTag)
	{
		componentTag = super.beforeProcessTag(componentTag);
		
		boolean previousAutoState = getAutoState();
		
		// do push of the tag from autoStack
		if(componentTag.isOpen() || componentTag.isOpenClose())
		{
			String auto = extractAttribute(componentTag, "auto");
			if (auto != null)
				autoGenerateStack.push(new AutoGenerateStackEntry(componentTag, Boolean.parseBoolean(auto)));
			
			// open the open-close head
			if(isHTMLElement(componentTag, "head") && componentTag.isOpenClose())
			{
				// convert head to open
				componentTag.setType(XmlTag.OPEN);
				componentTag.setModified(true);
				
				// push head close as next tag
				ComponentTag closeHeadTag = createComponentTag(componentTag, null, "head", XmlTag.CLOSE);
				closeHeadTag.setOpenTag(componentTag);
				pushTag(closeHeadTag);
			}
		}
		
		// for open components
		if (componentTag.isOpen())
		{
			if(isHTMLElement(componentTag, "body") && previousAutoState && !existsHead)
			{
				// head must be included before body tag.
				componentTag.setModified(true);
				pushTag(componentTag);
				
				// process the open head tag.
				componentTag = createComponentTag(componentTag, null, "head", XmlTag.OPEN);
				
				// push the close head tag.
				ComponentTag closeHeadTag = createComponentTag(componentTag, null, "head", XmlTag.CLOSE);
				closeHeadTag.setOpenTag(componentTag);
				pushTag(closeHeadTag);
			}
			else if(isHTMLElement(componentTag, "title") && getAutoState() && !titleIncluded)
			{
				// insert the attribute to render the title dynamically.
				componentTag.put(getNamespacePrefix() + ":title", true);
			}
			else if(isHTMLElement(componentTag, "html") && getAutoState())
			{
				// insert the attribute to render the locale dynamically.
				componentTag.put(getNamespacePrefix() + ":html", true);
			}
		}
		// for close components
		if (componentTag.isClose())
		{
			if (isHTMLElement(componentTag, "body") && getAutoState() && !bodyFooterIncluded)
			{
				componentTag.setModified(true);
				pushTag(componentTag);
				
				// bodyFooter must be included in the body before end body tag.
				componentTag = createComponentTag(componentTag, getNamespacePrefix(), "bodyFooter", XmlTag.OPEN_CLOSE);
			}
			else if (isHTMLElement(componentTag, "head") && getAutoState())
			{
				if(!headLinksIncluded)
				{
					componentTag.setModified(true);
					pushTag(componentTag);
					
					// headLinks must be included in the head before end head tag.
					componentTag = createComponentTag(componentTag, getNamespacePrefix(), "links", XmlTag.OPEN_CLOSE);
				}
				if(!headMetasIncluded)
				{
					componentTag.setModified(true);
					pushTag(componentTag);
					
					// headMetas must be included in the head before links tag.
					componentTag = createComponentTag(componentTag, getNamespacePrefix(), "metas", XmlTag.OPEN_CLOSE);
				}
				if(!titleIncluded)
				{
					componentTag.setModified(true);
					pushTag(componentTag);
					
					// title must be included in the head before metas tag.
					componentTag = createComponentTag(componentTag, null, "title", XmlTag.OPEN_CLOSE);
					// insert the attribute to render the title dynamically.
					componentTag.put(getNamespacePrefix() + ":title", true);
				}
			}
		}
		
		return componentTag;
	}

	@Override
	protected void afterProcessTag(ComponentTag componentTag)
	{
		if(componentTag.isOpen())
		{
			if (isHTMLElement(componentTag, "body") && getAutoState() && bodyHeaderComponentTag == null)
			{
				componentTag.setModified(true);
				// create a body header and put on the stack
				ComponentTag bodyHeaderComponentTag = createComponentTag(componentTag, getNamespacePrefix(), "bodyHeader", XmlTag.OPEN_CLOSE);
				bodyHeaderComponentTag.setModified(true);
				
				pushTag(bodyHeaderComponentTag);
			}
			else if (isHTMLElement(componentTag, "head") && getAutoState() && headScriptsComponentTag == null)
			{
				componentTag.setModified(true);
				// create a head scripts and put on the stack
				ComponentTag headScriptsComponentTag = createComponentTag(componentTag, getNamespacePrefix(), "headScripts", XmlTag.OPEN_CLOSE);
				headScriptsComponentTag.setModified(true);
				
				pushTag(headScriptsComponentTag);
			}
		}

		// verifies if the tag is inserted
		if (isHTMLElement(componentTag, "head"))
			existsHead = true;
		else if (isHTMLElement(componentTag, "title"))
			titleIncluded = true;
		else if (componentTag instanceof LayoutFileTag)
		{
			if ("bodyHeader".equals(componentTag.getName()))
			{
				// if the body header already exists ignore the previous
				// generated automatically
				if(bodyHeaderComponentTag == null)
					 bodyHeaderComponentTag = componentTag;
				 else
					 bodyHeaderComponentTag.setIgnore(true);
			}
			else if ("headScripts".equals(componentTag.getName()))
			{
				// if the head scripts already exists ignore the previous
				// generated automatically
				if(headScriptsComponentTag == null)
					headScriptsComponentTag = componentTag;
				 else
					 headScriptsComponentTag.setIgnore(true);
			}
			else if ("bodyFooter".equals(componentTag.getName()))
				bodyFooterIncluded = true;
			else if ("metas".equals(componentTag.getName()))
				headMetasIncluded = true;
			else if ("links".equals(componentTag.getName()))
				headLinksIncluded = true;
		}
		
		// do pop of the tag from autoStack when end the process
		if(componentTag.isClose() || componentTag.isOpenClose())
		{
			if(!autoGenerateStack.isEmpty() && componentTag.closes(autoGenerateStack.lastElement().getTag()))
			{
				autoGenerateStack.pop();
			}
		}
		
		super.afterProcessTag(componentTag);
	}
	
	/**
	 * Returns the current auto generate state.
	 * 
	 * @return the auto generate attribute value.
	 * @since 6.0.0
	 */
	private boolean getAutoState()
	{
		try
		{
			return autoGenerateStack.peek().isAutoGenerate();
		}
		catch (EmptyStackException e)
		{
			// default is true
			return true;
		}
	}
}